using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.DirectX;
using FileHelper;
using System.Runtime.InteropServices;

namespace Scenario_Editor.WR2TerrainLoader
{
    public class WR2Surface
    {
        long positionInFile;

        internal short texture1ID;
        internal short texture2ID;
        internal short texture3ID;
        internal short mappingMode;
        internal Mappings mappings1;
        internal Mappings mappings2;
        internal Mappings mappings3;

        public WR2Surface()
        {
            texture1ID = 0;
            texture2ID = 0;
            texture3ID = 0;
            mappingMode = 0;

            mappings1 = new Mappings();
            mappings2 = new Mappings();
            mappings3 = new Mappings();
        }

        public static WR2Surface FromStream(BinaryReader br)
        { 
            WR2Surface surface = new WR2Surface();
            surface.positionInFile = br.BaseStream.Position;
            surface.texture1ID = br.ReadInt16();
            surface.texture2ID = br.ReadInt16();
            surface.texture3ID = br.ReadInt16();
            surface.mappingMode = br.ReadInt16();

            surface.mappings1 = Mappings.FromStream(br);
            surface.mappings2 = Mappings.FromStream(br);
            surface.mappings3 = Mappings.FromStream(br);

            br.BaseStream.Position += 3 * 4;
            //surface.mappings1.CRC = br.ReadUInt32();
            //surface.mappings2.CRC = br.ReadUInt32();
            //surface.mappings3.CRC = br.ReadUInt32();
            return surface; 
        }

        public void SaveToStream(BinaryWriter bw)
        {
            bw.Write(this.texture1ID);
            bw.Write(this.texture2ID);
            bw.Write(this.texture3ID);
            bw.Write(this.mappingMode);

            this.mappings1.SaveToStream(bw);
            this.mappings2.SaveToStream(bw);
            this.mappings3.SaveToStream(bw);

            bw.Write(this.mappings1.CRC);
            bw.Write(this.mappings2.CRC);
            bw.Write(this.mappings3.CRC);
        }

        //internal void Save(BinaryWriter bw)
        //{
        //    bw.Write(this.texture1ID);
        //    bw.Write(this.texture2ID);
        //    bw.Write(this.texture3ID);
        //    bw.Write(this.mappingMode);

        //    this.mappings1.Write(bw);
        //    this.mappings2.Write(bw);
        //    this.mappings3.Write(bw);

        //    bw.Write((uint)this.mappings1.CRC);
        //    bw.Write((uint)this.mappings2.CRC);
        //    bw.Write((uint)this.mappings3.CRC);
        //}

        public class Mappings
        {
            internal Vector4 mappings1;
            internal Vector4 mappings2;
            float offsetX, offsetY, rotationX, rotationY, rotationZ, scaleX, scaleY; 

            uint crc;

            public Mappings()
            {
                this.mappings1 = Vector4.Empty;
                this.mappings2 = Vector4.Empty;
                
                this.offsetX = 0.0f;
                this.offsetY = 0.0f;
                this.rotationX = 0.0f;
                this.rotationY = 0.0f;
                this.rotationZ = 0.0f;
                this.scaleX = 10.0f;
                this.scaleY = 10.0f;
                
                UpdateVectors();
                UpdateCRC();
            }

            private void UpdateVectors()
            {
                //ax:=rotH/180*pi;
                double ax = this.rotationX / 180 * Math.PI;
                //ay:=rotP/180*pi;
                double ay = this.rotationY / 180 * Math.PI;
                //az:=rotB/180*pi;
                double az = this.rotationZ / 180 * Math.PI;
                //sX:=1/sX;
                double sX = 1 / this.scaleX;
                //sY:=1/sY;
                double sY = 1 / this.scaleY;

                //a:=cos(ax); b:=sin(ax);
                double a = Math.Cos(ax); double b = Math.Sin(ax);
                //c:=cos(ay); d:=sin(ay);
                double c = Math.Cos(ay); double d = Math.Sin(ay);
                //e:=cos(az); f:=sin(az);
                double e = Math.Cos(az); double f = Math.Sin(az);

                //with Material[MatID] do begin
                //Matrix[Lay,1,1]:=C*E*sx;
                this.mappings1.X = (float)(c * e * sX);
                //Matrix[Lay,1,2]:=-C*F*sx;
                this.mappings1.Y =(float)(-c * f * sX);
                //Matrix[Lay,1,3]:=D*sx;
                this.mappings1.Z = (float)(d * sX);
                //Matrix[Lay,2,1]:=(-A*D*E+B*F)*sy;
                this.mappings2.X = (float)((-a * d * e + b * f) * sY);
                //Matrix[Lay,2,2]:=(A*D*F+B*E)*sy;
                this.mappings2.Y =(float)( (a * d * f + b * e) * sY);
                //Matrix[Lay,2,3]:=(A*C)*sy;
                this.mappings2.Z = (float)(a * c * sY);
                //Matrix[Lay,1,4]:=TexMoveX.Value;
                this.mappings1.W = this.offsetX;
                //Matrix[Lay,2,4]:=TexMoveY.Value;
                this.mappings2.W = this.offsetY;
            }

            public static Mappings FromStream(BinaryReader br)
            {
                Mappings mappings = new Mappings();

                System.Diagnostics.Debugger.Log(1, "1", "Surface Matrix Start at:  " + br.BaseStream.Position.ToString() + "\n" );

                mappings.mappings1.X = br.ReadSingle();
                mappings.mappings1.Y = br.ReadSingle();
                mappings.mappings1.Z = br.ReadSingle();
                mappings.mappings1.W = br.ReadSingle();

                mappings.mappings2.X = br.ReadSingle();
                mappings.mappings2.Y = br.ReadSingle();
                mappings.mappings2.Z = br.ReadSingle();
                mappings.mappings2.W = br.ReadSingle();

                return mappings;
            }

            internal uint CRC
            {
                get
                {
                    return (uint)this.crc;                    
                }
            }

            public float OffsetX
            {
                get
                {
                    return this.mappings1.W;
                }
                set
                {
                    this.offsetX = value;
                    UpdateVectors();
                    UpdateCRC();
                }
            }

            public float OffsetY
            {
                get
                {
                   return  this.mappings2.W;
                }
                set
                {
                    this.offsetY = value;
                    UpdateVectors();
                    UpdateCRC();

                }
            }

            public float ScaleX
            {
                get
                {
                    double scaleX = 1 / Math.Sqrt(this.mappings1.X * this.mappings1.X + this.mappings1.Y * this.mappings1.Y + this.mappings1.Z * this.mappings1.Z);
                    return (float)scaleX;
                }
                set
                {
                    this.scaleX = value;
                    Vector3 coordinates = new Vector3(this.mappings1.X, this.mappings1.Y, this.mappings1.Z);
                    coordinates.Normalize();
                    coordinates.Scale(1 / this.scaleX);
                    this.mappings1.X = coordinates.X;
                    this.mappings1.Y = coordinates.Y;
                    this.mappings1.Z = coordinates.Z;
                    UpdateCRC();
                }
            }
            
            private void UpdateCRC()
            {
                AdlerChecksum adlerChecksum = new AdlerChecksum();
                
                byte[] data = new byte[8 * 4];
                BitConverter.GetBytes(this.mappings1.X).CopyTo(data, 0);
                BitConverter.GetBytes(this.mappings1.Y).CopyTo(data, 4);
                BitConverter.GetBytes(this.mappings1.Z).CopyTo(data, 8);
                BitConverter.GetBytes(this.mappings1.W).CopyTo(data, 12);

                BitConverter.GetBytes(this.mappings2.X).CopyTo(data, 16);
                BitConverter.GetBytes(this.mappings2.Y).CopyTo(data, 20);
                BitConverter.GetBytes(this.mappings2.Z).CopyTo(data, 24);
                BitConverter.GetBytes(this.mappings2.W).CopyTo(data, 28);

                adlerChecksum.MakeForBuff(data);
                this.crc = adlerChecksum.ChecksumValue;
            }

            public float ScaleY
            {
                get
                {
                    double scaleY = 1 / Math.Sqrt(this.mappings2.X * this.mappings2.X + this.mappings2.Y * this.mappings2.Y + this.mappings2.Z * this.mappings2.Z);
                    return (float)scaleY;
                }
                set
                {
                    this.scaleY = value;
                    Vector3 coordinates = new Vector3(this.mappings2.X, this.mappings2.Y, this.mappings2.Z);
                    coordinates.Normalize();
                    coordinates.Scale(1 / this.scaleY);
                    this.mappings2.X = coordinates.X;
                    this.mappings2.Y = coordinates.Y;
                    this.mappings2.Z = coordinates.Z;
                    UpdateCRC();
                }
            }

            public float RotationX
            {
                get
                {
                    return this.rotationX;
                }
                set
                {
                    this.rotationX = value;
                    UpdateVectors();
                    UpdateCRC();
                }
            }

            public float RotationY
            {
                get
                {
                   // double angle3 = Math.Atan2((double)this.mappings1.Z, (double)this.mappings1.X);// sin + cos;
                   //// double angle3 = Math.Atan2((double)matrix.M13, (double)matrix.M11);// sin + cos;
                   // angle3 = angle3 * 180 / Math.PI;
                   // return (float)angle3;
                    return this.rotationY;
                }
                set
                {
                    this.rotationY = value;
                    UpdateVectors();
                    UpdateCRC();
                }
            }

            public float RotationZ
            {
                get
                {
                    //double angle = Math.Atan2(-(double)this.mappings1.Y, (double)this.mappings1.X);// sin + cos;
                    //// double angle = Math.Atan2(-(double)matrix.M12, (double)matrix.M11);// sin + cos;
                    //angle = angle * 180 / Math.PI;
                    //return (float)angle;
                    return this.rotationZ;
                }
                set
                {
                    this.rotationZ = value;
                    UpdateVectors();
                    UpdateCRC();
                }
            }
            
            internal void Write(BinaryWriter bw)
            {

                bw.Write(this.mappings1.X);
                bw.Write(this.mappings1.Y);
                bw.Write(this.mappings1.Z);
                bw.Write(this.mappings1.W);

                bw.Write(this.mappings2.X);
                bw.Write(this.mappings2.Y);
                bw.Write(this.mappings2.Z);
                bw.Write(this.mappings2.W);
            }

            internal void SaveToStream(BinaryWriter bw)
            {
                Write(bw);
            }
        }
        
    }
}
